package com.naiterui.ehp.bs.esign.api.bjca.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;

import com.alibaba.fastjson.JSONObject;
import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.utils.biz.ScheduleUtils;
import com.naiterui.ehp.bp.utils.date.DateUtil;
import com.naiterui.ehp.bp.utils.encypt.MD5;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bp.utils.net.RestHttpUtil;
import com.naiterui.ehp.bs.esign.api.bjca.vo.YwxAccessToken;
import com.naiterui.ehp.bs.esign.api.bjca.vo.YwxHeadParamVO;
import com.naiterui.ehp.bs.esign.api.bjca.vo.YwxParamVO;
import com.naiterui.ehp.bs.esign.api.bjca.vo.YwxResponseVO;
import com.naiterui.ehp.bs.esign.api.bjca.vo.YwxSignParamVO;
import com.naiterui.ehp.bs.esign.api.bjca.vo.YwxSignStatusVO;
import com.naiterui.ehp.bs.esign.api.bjca.vo.YwxUserStatusVO;
import com.naiterui.ehp.bs.esign.api.bjca.vo.YwxUserVO;
import com.naiterui.ehp.bs.esign.exception.ExceptionCodes;
import com.naiterui.ehp.bs.esign.utils.Constants;

/**
 * @author guoyongxiang
 * @date 2019/11/22 14:03
 * @since 1.0.0
 */
public class SignYwxUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(SignYwxUtil.class);

    public static final String CLIENT_ID = ConfigUtil.getString("esign.ywx.clientId");
    public static final String APP_SECRET = ConfigUtil.getString("esign.ywx.secret");
    private static final String API_HOSTS = ConfigUtil.getString("esign.ywx.apiHosts");
    private static final String SERVICE_ID_USER = "100";
    private static final String SERVICE_ID_SIGN = "101";

    public static final String USER_TYPE_DR = "医生";
    public static final String USER_TYPE_PH = "药师";

    private static String ACCESS_TOKEN_REFRESH_URL = ConfigUtil.getString("esign.ywx.accessToken.refresh");
    ;

    private final static String RDS_ACCESS_TOKEN_CACHE = CommonConstant.REDIS_PRE_RECOM + "esign_ywx_accessToken";

    /**
     * 获取accessToken
     */
    private final static String ACCESS_TOKEN_URL = API_HOSTS + "/device/v2/server/oauth/getAccessToken";
    /**
     * 用户信息同步
     */
    private final static String USER_SYN = API_HOSTS + "/am/v2/doctor/syn";
    /**
     * 用户状态查询
     */
    private final static String USER_STATUS = API_HOSTS + "/am/v2/doctor/getUserStatus";
    /**
     * 获取签名状态
     */
    private final static String SIGN_STATUS_URL = API_HOSTS + "/am/v2/recipe/getSignOrderStatus";
    /**
     * 上传PDF待签文件
     */
    private final static String RECIPE_SYN = API_HOSTS + "/am/v2/recipe/syn";

    /**
     * 获取医网信access token
     *
     * @return
     *
     * @throws BusinessException
     */
    public static String accessToken() throws BusinessException {
        String accessToken = Optional.ofNullable(CLIENT_ID)
                                     .map(s -> RDS_ACCESS_TOKEN_CACHE)
                                     .map(cacheKey -> RedisUtil.valueOps().getString(cacheKey))
                                     .orElse(null);
        if (null == accessToken) {
            accessToken = SignYwxUtil.refreshAccessToken().getAccessToken();
        }

        return accessToken;
    }

    public static YwxAccessToken refreshAccessToken() throws BusinessException {
        Map<String, String> paramMap = new HashMap<>(2);
        paramMap.put("clientId", CLIENT_ID);
        paramMap.put("appSecret", APP_SECRET);

        ResponseEntity<YwxResponseVO<YwxAccessToken>> responseEntity =
                RestHttpUtil.get().url(ACCESS_TOKEN_URL).paramMap(paramMap).execute(new ParameterizedTypeReference<YwxResponseVO<YwxAccessToken>>() {
                });

        YwxResponseVO<YwxAccessToken> accessTokenResult = responseEntity.getBody();
        if (accessTokenResult == null || !accessTokenResult.isSuccess()) {
            LOGGER.warn("请求医网信 AccessToken失败，结果：{}", accessTokenResult);
            throw new BusinessException(ExceptionCodes.ESIGN_AUTH_REQUEST_FAILED, "BJCA:" + accessTokenResult.getMessage());
        }

        String accessToken = accessTokenResult.getData().getAccessToken();
        Integer expireSecond = Integer.valueOf(accessTokenResult.getData().getExpiresIn());

        RedisUtil.valueOps().setString(RDS_ACCESS_TOKEN_CACHE, accessToken, expireSecond);

        Date refreshTime = DateUtil.addSeconds(new Date(), expireSecond - 120);
        String refreshTimeStr = DateUtil.formatDate(refreshTime, DateUtil.CRON_TIME_FORMAT);
        Map<String, String> scheduleParam = new HashMap<>(1);
        try {
            //先删除未执行的刷新任务
            ScheduleUtils.removeCustomerTask(ConfigUtil.getString(Constants.TASK_SCHEDULE_HOST), ScheduleUtils.TASK_TYPE_ESIGN_ACCESS_TOKEN,
                    CLIENT_ID);
            //添加新的刷新任务
            ScheduleUtils.addCustomerTask(ConfigUtil.getString(Constants.TASK_SCHEDULE_HOST), refreshTimeStr,
                    ScheduleUtils.TASK_TYPE_ESIGN_ACCESS_TOKEN, CLIENT_ID, ACCESS_TOKEN_REFRESH_URL, scheduleParam);
        } catch (Exception e) {
            LOGGER.error("accessToken刷新任务添加失败，errmsg={}", e);
        }

        return accessTokenResult.getData();
    }

    /**
     * 医生、药师信息同步
     *
     * @param name
     * @param idCard
     * @param phone
     * @param department
     *
     * @return
     *
     * @throws BusinessException
     */
    public static YwxUserVO synUser(String name, String idCard, String phone, String hospital, String title) throws BusinessException {
        Map<String, String> body = new HashMap(8);
        body.put("orgName", hospital);
        body.put("doctorName", name);
        body.put("uid", idCard);
        body.put("uidCardType", "SF");
        body.put("phone", phone);
        body.put("title", title);
        if ("药师".equals(title)) {
            body.put("doctorIdType", "YJ");
        } else {
            body.put("doctorIdType", "YS");
        }
        body.put("department", title);
        YwxResponseVO<YwxUserVO> responseVO = post(SERVICE_ID_USER, USER_SYN, body, new ParameterizedTypeReference<YwxResponseVO<YwxUserVO>>() {
        });
        if (!responseVO.isSuccess()) {
            LOGGER.warn("医网信-获取用户状态失败 name:{}, ywxCode:{}, msg:{}", name, responseVO.getStatus(), responseVO.getMessage());
            throw new BusinessException(ExceptionCodes.ESIGN_USER_SYNC_FAILED, responseVO.getMessage());
        }
        return responseVO.getData();
    }

    /**
     * 获取用户审核状态
     *
     * @param openId
     *
     * @return
     *
     * @throws BusinessException
     */
    public static String getUserStatus(String openId) throws BusinessException {
        Map<String, String> param = new HashMap(3);
        param.put("openId", openId);
        param.put("clientId", CLIENT_ID);
        param.put("accessToken", accessToken());

        String postBody = JSONObject.toJSONString(param);
        YwxResponseVO<Map<String, String>> responseVO = RestHttpUtil.post()
                                                                    .body(postBody)
                                                                    .url(USER_STATUS)
                                                                    .execute(new ParameterizedTypeReference<YwxResponseVO<Map<String, String>>>() {
                                                                    })
                                                                    .getBody();
        if (!responseVO.isSuccess()) {
            LOGGER.warn("医网信-获取用户状态失败 openId:{}, ywxCode:{}, msg:{}", openId, responseVO.getStatus(), responseVO.getMessage());
            throw new BusinessException(ExceptionCodes.ESIGN_USER_SYNC_FAILED, responseVO.getMessage());
        }
        return responseVO.getData().get("userStatus");

    }

    /**
     * 获取用户审核状态
     * @param openId
     * @return
     * @throws BusinessException
     */
    public static YwxUserStatusVO getUserStatusDetail(String openId) throws BusinessException {
        Map<String, String> param = new HashMap(3);
        param.put("openId", openId);
        param.put("clientId", CLIENT_ID);
        param.put("accessToken", accessToken());


        String postBody = JSONObject.toJSONString(param);
        YwxResponseVO<YwxUserStatusVO> responseVO = RestHttpUtil.post()
                                                                    .body(postBody)
                                                                    .url(USER_STATUS)
                                                                    .execute(new ParameterizedTypeReference<YwxResponseVO<YwxUserStatusVO>>() {
                                                                    })
                                                                    .getBody();
        if (!responseVO.isSuccess()) {
            LOGGER.warn("医网信-获取用户状态失败 openId:{}, ywxCode:{}, msg:{}", openId, responseVO.getStatus(), responseVO.getMessage());
            throw new BusinessException(ExceptionCodes.ESIGN_USER_SYNC_FAILED, responseVO.getMessage());
        }
        return responseVO.getData();

    }

    /**
     * 签名状态查询
     *
     * @param uniqueId
     *
     * @return
     *
     * @throws BusinessException
     */
    public static YwxSignStatusVO getSignStatus(String uniqueId) throws BusinessException {
        Map<String, String> paramMap = new HashMap<>(3);
        paramMap.put("uniqueId", uniqueId);

        YwxResponseVO<YwxSignStatusVO> responseVO = get(SIGN_STATUS_URL, paramMap, new ParameterizedTypeReference<YwxResponseVO<YwxSignStatusVO>>() {
        });
        if (!responseVO.isSuccess()) {
            LOGGER.warn("医网信-获取签名状态失败 uniqueId:{}, ywxCode:{}, msg:{}", uniqueId, responseVO.getStatus(), responseVO.getMessage());
            throw new BusinessException(ExceptionCodes.ESIGN_GET_SIGN_RESULT_FIALED, responseVO.getMessage());
        }
        return responseVO.getData();
    }

    /**
     * 上传PDF
     *
     * @param signParamVO
     *
     * @return
     *
     * @throws BusinessException
     */
    public static String synPdf(YwxSignParamVO signParamVO) throws BusinessException {
        Map<String, Object> param = new HashMap<>(2);
        // 签名方式：1：SDK 集成签名
        param.put("signType", 1);

        YwxHeadParamVO head = getHead(SERVICE_ID_SIGN);
        head.setTemplateId("personPdf");
        YwxParamVO msg = YwxParamVO.builder().head(head).body(signParamVO).build();
        param.put("msg", msg);


        String postBody = JSONObject.toJSONString(param);
        YwxResponseVO<Map<String, String>> responseVO = RestHttpUtil.post()
                                                                    .body(postBody)
                                                                    .url(RECIPE_SYN)
                                                                    .execute(new ParameterizedTypeReference<YwxResponseVO<Map<String, String>>>() {
                                                                    })
                                                                    .getBody();
        if (!responseVO.isSuccess()) {
            LOGGER.warn("医网信-上传待签PDF失败 urId:{}, ywxCode:{}, msg:{}", signParamVO.getUrId(), responseVO.getStatus(), responseVO.getMessage());
            throw new BusinessException(ExceptionCodes.ESIGN_GET_SIGN_RESULT_FIALED, responseVO.getMessage());
        }
        return responseVO.getData().get("uniqueId");
    }

    /**
     * 回调验签
     *
     * @param body
     * @param sign
     *
     * @return
     */
    public static boolean verifySign(String body, String sign) {
        Map<String, String> bodyMap = new HashMap<String, String>();

        JSONObject jsonObj = JSONObject.parseObject(body);
        // 遍历json，将key 和value 存入map
        for (Map.Entry<String, Object> entry : jsonObj.entrySet()) {
            if (entry.getValue() == null) {
                continue;
            }
            bodyMap.put(entry.getKey(), entry.getValue().toString());
        }
        String thirdSign = buildThirdSign(bodyMap, APP_SECRET);
        if (StringUtils.isNotBlank(thirdSign) && thirdSign.equals(sign)) {
            return true;
        }
        LOGGER.warn("医网信, 签名错误, body:{}, sign:{}, thirdSign:{}", body, sign, thirdSign);
        return false;
    }

    private static String buildThirdSign(Map<String, String> mapBody, String clientSecret) {
        try {
            StringBuffer paramStr = new StringBuffer();
            String resultStr = "";
            // 字母排序,使用key=value,并用&分割的形式拼接字典排序字符串
            if (mapBody != null && mapBody.size() > 0) {
                List<String> paramsKeyList = new ArrayList<String>(mapBody.keySet());
                Collections.sort(paramsKeyList);
                for (String key : paramsKeyList) {
                    String value = String.valueOf(mapBody.get(key));
                    if (value != null && !"".equals(value)) {
                        paramStr.append("&" + key);
                        paramStr.append("=" + value);
                    }
                }
            }
            // 去掉最左侧第一个&符号，并用#把clientSecret 与字典排序字符串进行拼接
            String signSourceStr = paramStr.substring(1) + "#" + clientSecret;
            // 计算字符串md5 16 进制字符串，此值就是回调数据中的sign
            resultStr = MD5.md5(signSourceStr);
            return resultStr;
        } catch (Exception e) {
            LOGGER.error("医网信，签名计算异常", e);
        }
        return clientSecret;
    }

    /**
     * 获取公共参数Head
     *
     * @param serviceId
     *
     * @return
     *
     * @throws BusinessException
     */
    private static YwxHeadParamVO getHead(String serviceId) throws BusinessException {
        String accessToken = SignYwxUtil.accessToken();
        return YwxHeadParamVO.builder().accessToken(accessToken).clientId(CLIENT_ID).serviceId(serviceId).build();
    }

    /**
     * GET
     *
     * @param url
     * @param param
     *
     * @return
     *
     * @throws BusinessException
     */
    private static <T> T get(String url, Map<String, String> param, ParameterizedTypeReference<T> responseTypeReference) throws BusinessException {
        String accessToken = SignYwxUtil.accessToken();
        param.put("clientId", CLIENT_ID);
        param.put("accessToken", accessToken);
        return RestHttpUtil.get().paramMap(param).url(url).execute(responseTypeReference).getBody();
    }

    private static <T> T post(String url, Map<String, String> param, ParameterizedTypeReference<T> responseTypeReference) throws BusinessException {
        String accessToken = SignYwxUtil.accessToken();
        param.put("clientId", CLIENT_ID);
        param.put("accessToken", accessToken);
        return RestHttpUtil.post().paramMap(param).url(url).execute(responseTypeReference).getBody();
    }

    /**
     * POST请求
     *
     * @param serviceId
     * @param url
     * @param body
     *
     * @return
     *
     * @throws BusinessException
     */
    private static <T> T post(String serviceId, String url, Object body, ParameterizedTypeReference<T> responseTypeReference) throws BusinessException {
        YwxHeadParamVO headParamVO = getHead(serviceId);
        YwxParamVO paramVO = YwxParamVO.builder().head(headParamVO).body(body).build();
        String postBody = JSONObject.toJSONString(paramVO);
        return RestHttpUtil.post().body(postBody).url(url).execute(responseTypeReference).getBody();
    }

    public static String encodeBase64File(String path) throws Exception {
        File file = new File(path);
        byte[] buffer;
        try (FileInputStream inputFile = new FileInputStream(file)) {
            buffer = new byte[(int) file.length()];
            inputFile.read(buffer);
        }
        return Base64.encodeBase64String(buffer);
    }

    /**
     * <p>将base64字符解码保存文件</p>
     *
     * @param base64Code
     * @param targetPath
     *
     * @throws Exception
     */
    public static void decoderBase64File(String base64Code, String targetPath) throws Exception {
        byte[] buffer = Base64.decodeBase64(base64Code);
        File file = new File(targetPath);
        if (!file.exists()) {
            FileUtils.forceMkdirParent(file);
        }
        try (FileOutputStream out = new FileOutputStream(targetPath)) {
            out.write(buffer);
        }
    }
}
